﻿namespace TomatoBreak.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Web.Mvc;
    using System.Web.Routing;

    using ExtensionMethods;

    public static class RESTfulRouteHelper
    {
        private static readonly IEnumerable<string> idBasedActions = new[] { "destroy", "update", "show" };

        public static RouteCollection Resources<TController>(this RouteCollection instance) where TController : Controller
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            var controllerType = typeof(TController);
            var controllerName = ControllerName(controllerType);
            var actions = ControllerActions(controllerType);

            foreach (var method in actions)
            {
                var actionName = ActionName(method);
                var routeName = "{0}-{1}".Interpolate(controllerName, actionName);

                string url;
                object constraints;

                if (idBasedActions.Contains(actionName, StringComparer.Ordinal))
                {
                    var httpMethod = HttpVerbs.Get;

                    if (actionName.Equals("destroy", StringComparison.Ordinal))
                    {
                        httpMethod = HttpVerbs.Delete;
                    }
                    else if (actionName.Equals("update", StringComparison.Ordinal))
                    {
                        httpMethod = HttpVerbs.Put;
                    }

                    url = controllerName;

                    var firstParameter = method.GetParameters().FirstOrDefault();

                    if (firstParameter != null && firstParameter.Name.Equals("id", StringComparison.OrdinalIgnoreCase) && firstParameter.ParameterType == typeof(Guid))
                    {
                        constraints = new { id = new GuidConstraint(), httpMethod = new RESTfulHttpVerbConstraint(httpMethod) };
                        url += "/{id}";
                    }
                    else
                    {
                        constraints = new { httpMethod = new RESTfulHttpVerbConstraint(httpMethod) };
                    }
                }
                else if (actionName.Equals("create", StringComparison.Ordinal))
                {
                    url = controllerName;
                    constraints = new { httpMethod = new RESTfulHttpVerbConstraint(HttpVerbs.Post) };
                }
                else
                {
                    continue;
                }

                instance.MapRoute(routeName.ToLower(CultureInfo.CurrentCulture), url.ToLower(CultureInfo.CurrentCulture), new { controller = controllerName, action = actionName }, constraints);
            }

            return instance;
        }

        public static RouteCollection Resource<TController>(this RouteCollection instance) where TController : Controller
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            var controllerType = typeof(TController);
            var controllerName = ControllerName(controllerType);
            var actions = ControllerActions(controllerType);

            foreach (MethodInfo method in actions)
            {
                var actionName = ActionName(method);

                var routeName = "{0}-{1}".Interpolate(controllerName, actionName);

                HttpVerbs httpMethod;

                if (actionName.Equals("destroy", StringComparison.Ordinal))
                {
                    httpMethod = HttpVerbs.Delete;
                }
                else if (actionName.Equals("update", StringComparison.Ordinal))
                {
                    httpMethod = HttpVerbs.Put;
                }
                else if (actionName.Equals("create", StringComparison.Ordinal))
                {
                    httpMethod = HttpVerbs.Post;
                }
                else if (actionName.Equals("show", StringComparison.Ordinal))
                {
                    httpMethod = HttpVerbs.Get;
                }
                else
                {
                    continue;
                }

                var url = controllerName;
                var constraints = new { httpMethod = new RESTfulHttpVerbConstraint(httpMethod) };

                instance.MapRoute(routeName.ToLower(CultureInfo.CurrentCulture), url.ToLower(CultureInfo.CurrentCulture), new { controller = controllerName, action = actionName }, constraints);
            }

            return instance;
        }

        private static string ActionName(MethodInfo methodInfo)
        {
            var actionName = (methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true)
                                        .OfType<ActionNameAttribute>()
                                        .Select(attribute => attribute.Name)
                                        .FirstOrDefault() ?? methodInfo.Name)
                                        .ToLower(CultureInfo.CurrentCulture);

            return actionName;
        }

        private static IEnumerable<MethodInfo> ControllerActions(Type controllerType)
        {
            Func<MethodInfo, bool> match = methood => !methood.IsSpecialName &&
                                                      typeof(ActionResult).IsAssignableFrom(methood.ReturnType) &&
                                                      !methood.GetCustomAttributes(typeof(NonActionAttribute), true).Any();

            var actions = controllerType.GetMethods().Where(match).ToList();

            return actions;
        }

        private static string ControllerName(Type controllerType)
        {
            var controllerName = controllerType.Name
                                               .Substring(0, controllerType.Name .IndexOf("Controller", StringComparison.Ordinal))
                                               .ToLower(CultureInfo.CurrentCulture);

            return controllerName;
        }
    }
}